package com.aispeech.task.fsm;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;

import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class State implements Serializable {
    protected static final Map<String, State> nameEnumMap = new ConcurrentHashMap<>();
    protected final Transition[] transitions;
    @Getter
    protected String name;

    public State(String name) {
        this.name = name;
        this.transitions = new Transition[]{};
    }

    public State(Transition... transitions) {
        this.transitions = transitions;
    }

    public State(String name, Transition... transitions) {
        this.name = name;
        if (!nameEnumMap.containsKey(name)) {
            nameEnumMap.put(name, this);
        }

        this.transitions = transitions;
    }

    public static State valueOf(String name) {
        if (name == null)
            throw new NullPointerException("name is null");

        State state = nameEnumMap.get(name);

        if (state != null) {
            return state;
        } else {
            throw new IllegalArgumentException("No enum constant exists, name is : " + name);
        }
    }

    public Pair<State, Event> transition(Event event) {
        log.info("Firing event: < {} > ===> in State: [ {} ] now...", event.eventType(), name);
        Event next = event.fire();
        log.info("State: [ {} ] fired. next event: < {} >", name, next.eventType());
        return Pair.of(transition(next.eventType()), next);
    }

    public State transition(EventType type) {
        for (Transition t : transitions) {
            if (t.type() == type) {
                return t.target();
            }
        }

        return Invalid;
    }

    public boolean isTerminal() {
        return transitions.length == 0;
    }

    public boolean equals(State state) {
        return name != null && state != null && name.equals(state.name);
    }

    public String toString() {
        return name;
    }

    public static void init() {
    }

    public static final State Terminal = new State("Terminal");
    public static final State Invalid = new State("Invalid");
}
